(function(){

	phnq = typeof(phnq)=="undefined"?{}:phnq;

	phnq.http = phnq.createObject("http");

	var EVAL_BODY = 0;
	var SKIP_BODY = 1;
	var EVAL_BODY_AGAIN = 2;
	var templateIndexes = {};
	var templates = {};
	var tags = {};
	var i18ns = {};
	var ptypes = {};
	var nextPhnqId = 0;

	var tagFooterBuff = [];
	tagFooterBuff.push("this.doStartTag = (typeof(doStartTag) == \"function\" ? doStartTag : null);");
	tagFooterBuff.push("this.doEndTag = (typeof(doEndTag) == \"function\" ? doEndTag : null);");
	tagFooterBuff.push("this.doAfterBody = (typeof(doAfterBody) == \"function\" ? doAfterBody : null);");
	tagFooterBuff.push("this.reset = (typeof(reset) == \"function\" ? reset : null);");
	var tagFooter = tagFooterBuff.join("");

	var globalize = function(name, value)
	{
		this[name] = value;
	};

	var tagCtx =
	{
		getAttribute: function(name)
		{
			var val = this.attrs["_"+name];
			if(typeof(val) == "undefined")
				return null;
		
			if(typeof(val)=="string" && !isNaN(val))
			{
				if(val.match(/false/i))
					val = false;
				else if(val.match(/true/i))
					val = true;
				else if(val.match(/\./))
					val = parseFloat(val);
				else
			 		val = parseInt(val, 10);
			}
			
			return val;
		},
	
		expose: function(n, v, s)
		{
			switch(s||"local")
			{
				case "global":
					globalize(n, v);
					break;
				case "local":
					this.exposed[n] = v;
					break;
				case "session":
					// ???
					break;
			}
		}
	};

	var evalTag = function(type, attrs, parentTag, writeFn, ptypeIdx, bodyFn)
	{
		var tagFn = tags[type];
		var tp = tagFn.pool;
	
		var tag = tagFn.pool.length>0?tp.pop():new tagFn();
	
		tag.parentTag = parentTag;
		tag.params = {};
		
		for(var k in attrs)
		{
			tag.params[k.substring(1)] = attrs[k];
		}
	
		tagCtx.attrs = attrs;
		tagCtx.write = writeFn;
		tagCtx.exposed = {"_tag": tag};
		tagCtx.i18n = i18ns[ptypeIdx];
		tagCtx.ptype = ptypes[ptypeIdx];
		tagCtx.insertPhnqlet = function(type, params)
		{
			params = params||{};
			var tmplt = templates[templateIndexes[type+".main"]];
			if(tmplt)
			{
				tmplt(params);
			}
			else
			{
				var cls = params["class"];
				cls = cls ? "phnqlet_ph "+cls : "phnqlet_ph";
				writeFn("<ul class=\""+cls+"\" type=\""+type+"\">");
				for(var k in params)
				{
					writeFn("<li name=\""+k+"\" value=\""+params[k]+"\"></li>");
				}
				writeFn("</ul>");
			}
		};
	
		if(tag.doStartTag==null || tag.doStartTag(tagCtx)!=SKIP_BODY)
		{
			if(bodyFn)
			{
				do
				{
					var args = [];
					for(n in tagCtx.exposed)
					{
						args.push(n);
						args.push(tagCtx.exposed[n]);
					}
				
					bodyFn.apply(null, args);
				}
				while(tag.doAfterBody && tag.doAfterBody(tagCtx)==EVAL_BODY_AGAIN);
			}
		}
	
		if(tag.doEndTag)
			tag.doEndTag(tagCtx);

		if(tag.reset)
			tag.reset();
	
		tp.push(tag);
	};


	var _tag;
	var result = [];
	var _t = evalTag;

	var _ = function(str)
	{
		result.push(str);
	};

	phnq.runtime =
	{
		isTemplateLoaded: function(phnqletType, tmpltName)
		{
			var tmpltType = phnqletType + "." + (tmpltName||"main");
			return typeof(templateIndexes[tmpltType]) != "undefined";
		},
	
		getLoadedTmpltMask: function()
		{
			var bits = new phnq.util.Bitset();
			for(idx in templates)
			{
				bits.set(idx, true);
			}
			return bits.toString();
		},
	
		eval: function(objCode)
		{
			result.length = 0;
		
			_tag = null;
		
			var _expArgs = function(args)
			{
				var buff = [];
				var len = args.length;
				for(var i=0; i<len; i+=2)
				{
					buff.push("var ");
					buff.push(args[i]);
					buff.push("=");
					buff.push("arguments["+(i+1)+"];");
				}
				return buff.join("");
			};
		
			// client
			if(objCode)
			{
				eval(objCode);
			}
			
			// server
			/*RUNNABLE_CODE_PLACEHOLDER*/
		
			return result.join("");
		},
	
		getMarkup: function(phnqletType, tmpltName)
		{
			var tmpltType = phnqletType + "." + (tmpltName||"main");
			var tmpltIdx = templateIndexes[tmpltType];
			return phnq.runtime.eval("templates["+tmpltIdx+"]({});");
		}
	};

	var _print = phnq.print;
	phnq.print = function()
	{
		var buf = [];
		var len = arguments.length;
		for(var i=0; i<len; i++)
		{
			buf.push(phnq.serialize(arguments[i]));
		}
		_print(buf.join(" "));
	};
	
	phnq.serialize = function(obj)
	{
		var buf = [];
		_serialize(obj, buf);
		return buf.join("");
	};
	
	var _serialize = function(obj, buf)
	{
		var type = typeof(obj);
		if(type == "object" && obj instanceof Array)
			type = "array";
		
		switch(type)
		{
			case "string":
				buf.push("\""+obj+"\"");
				break;
			
			case "array":
				buf.push("[");
				var len = obj.length;
				for(var i=0; i<len; i++)
				{
					buf.push(i==0?"":", ");
					buf.push(phnq.serialize(obj[i]));
				}
				buf.push("]");
				break;
				
			case "object":
				buf.push("{");
				var i=0;
				for(var k in obj)
				{
					buf.push((i++)==0?"":", ");
					buf.push(k+": ");
					buf.push(phnq.serialize(obj[k]));
				}
				buf.push("}");
				break;
				
			case "function":
				buf.push("function() { /* body hidden */ }");
				break;
				
			default:
				buf.push(obj);
				break;
		}
	};

})();
